package io.youngledo.usb.storage.impl;

import io.youngledo.usb.storage.*;
import io.youngledo.usb.exception.StorageConnectionException;
import io.youngledo.usb.exception.StorageOperationException;
import io.youngledo.usb.task.ProgressInputStream;
import io.youngledo.usb.task.ProgressTracker;
import io.youngledo.usb.config.TimeoutConfig;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.minio.*;
import io.minio.messages.Bucket;
import io.minio.messages.Item;

import java.io.*;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;

/// MinIO存储服务适配器
/// 包含现代化的日志记录和异常处理
///
@Slf4j
public class MinioStorageService extends AbstractObjectStorageService {

    private MinioClient client;

    @Override
    public void connect(StorageConfig config) {
        if (config.type() != StorageType.MINIO) {
            throw new IllegalArgumentException("Invalid storage type for MinIO service: " + config.type());
        }

        this.config = config;

        // 创建自定义HTTP客户端配置，使用统一的超时配置
        okhttp3.OkHttpClient httpClient = new okhttp3.OkHttpClient.Builder()
                .connectTimeout(TimeoutConfig.getConnectionTimeoutSeconds(), java.util.concurrent.TimeUnit.SECONDS)
                .readTimeout(TimeoutConfig.getReadTimeoutSeconds(), java.util.concurrent.TimeUnit.SECONDS)
                .writeTimeout(TimeoutConfig.getWriteTimeoutSeconds(), java.util.concurrent.TimeUnit.SECONDS)
                .build();

        MinioClient.Builder clientBuilder = MinioClient.builder()
                .endpoint(config.getUrl())
                .credentials(config.accessKey(), config.secretKey())
                .httpClient(httpClient);

        // MinIO region是可选的，只有在非空时才设置
        if (config.region() != null && !config.region().trim().isEmpty()) {
            clientBuilder.region(config.region());
        }

        this.client = clientBuilder.build();

        // 测试连接
        try {
            client.listBuckets();
        } catch (Exception e) {
            throw new StorageConnectionException(e.getMessage(), e);
        }
        this.connected = true;
    }

    @Override
    protected void performDisconnect() {
        this.client = null;
    }

    @SneakyThrows
    @Override
    public List<StorageBucket> listBuckets() {
        checkConnection();
        return client.listBuckets()
                .parallelStream()
                .map(bucket -> StorageBucket.of(
                        bucket.name(),
                        bucket.creationDate() != null ?
                                bucket.creationDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
                                : null
                )).toList();
    }

    @Override
    public void createBucket(String bucketName) {
        checkConnection();
        try {
            MakeBucketArgs.Builder bucketBuilder = MakeBucketArgs.builder()
                    .bucket(bucketName);

            // MinIO region是可选的，只有在非空时才设置
            if (config.region() != null && !config.region().trim().isEmpty()) {
                bucketBuilder.region(config.region());
            }

            client.makeBucket(bucketBuilder.build());
        } catch (Exception e) {
            throw StorageOperationException.operationFailed("Failed to create bucket: " + e.getMessage(), e);
        }
    }

    @Override
    public void deleteBucket(String bucketName) {
        checkConnection();
        try {
            client.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            throw StorageOperationException.bucketNotFound(bucketName);
        }
    }

    @SneakyThrows
    @Override
    public List<StorageObject> listObjects(String bucketName, String prefix, boolean recursive) {
        checkConnection();
        Iterable<Result<Item>> results = client.listObjects(
                ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .recursive(recursive)
                        .build()
        );

        List<StorageObject> objects = new ArrayList<>();
        for (Result<Item> result : results) {
            Item item = result.get();

            String displayName = ObjectStorageHelper.processObjectKey(item.objectName(), prefix);
            if (displayName == null) {
                continue;
            }

            // 如果是目录，确保以/结尾
            if (item.isDir()) {
                displayName = ObjectStorageHelper.ensureDirectorySuffix(displayName);
            }

            LocalDateTime lastModified = null;
            if (!item.isDir()) {
                // 只有文件才有修改时间
                lastModified = item.lastModified().toLocalDateTime();
            }
            objects.add(new StorageObject(
                    item.objectName(), // key - 对象完整路径
                    displayName,       // name - 显示名称
                    item.size(),       // size - 文件大小
                    lastModified,      // lastModified - 最后修改时间
                    item.isDir(),      // isDirectory - 是否为目录
                    item.etag()        // etag - 实体标签
            ));
        }

        return objects;
    }

    @Override
    public void uploadFile(String bucketName, String objectKey, File file) {
        checkConnection();
        try {
            client.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectKey)
                            .filename(file.getAbsolutePath())
                            .build()
            );
        } catch (Exception e) {
            throw StorageOperationException.operationFailed("Failed to upload file: " + e.getMessage(), e);
        }
    }

    @Override
    public void uploadFile(String bucketName, String objectKey, InputStream inputStream, long contentLength) {
        checkConnection();
        try {
            client.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectKey)
                            .stream(inputStream, contentLength, -1)
                            .build()
            );
        } catch (Exception e) {
            throw StorageOperationException.operationFailed("Failed to upload file: " + e.getMessage(), e);
        }
    }

    @Override
    public InputStream downloadFile(String bucketName, String objectKey) {
        checkConnection();
        try {
            return client.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectKey)
                            .build()
            );
        } catch (Exception e) {
            throw StorageOperationException.objectNotFound(objectKey);
        }
    }

    @Override
    public void downloadFile(String bucketName, String objectKey, File localFile) {
        checkConnection();

        // 创建临时文件
        File tempFile = new File(localFile.getAbsolutePath() + ".tmp." + System.currentTimeMillis());

        try {
            client.downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectKey)
                            .filename(tempFile.getAbsolutePath())
                            .build()
            );

            // 下载成功后，将临时文件重命名为目标文件
            if (localFile.exists()) {
                Files.delete(localFile.toPath());
            }
            Files.move(tempFile.toPath(), localFile.toPath());

        } catch (Exception e) {
            // 如果下载失败，删除临时文件
            if (tempFile.exists()) {
                try {
                    Files.delete(tempFile.toPath());
                } catch (Exception deleteEx) {
                    log.warn("Failed to delete temporary file: {}", tempFile.getAbsolutePath());
                }
            }
            throw StorageOperationException.objectNotFound(objectKey);
        }
    }

    @Override
    public void deleteObject(String bucketName, String objectKey) {
        checkConnection();
        try {
            client.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectKey)
                            .build()
            );
        } catch (Exception e) {
            throw StorageOperationException.objectNotFound(objectKey);
        }
    }

    @Override
    public void deleteObjects(String bucketName, List<String> objectKeys) {
        checkConnection();
        try {
            for (String objectKey : objectKeys) {
                deleteObject(bucketName, objectKey);
            }
        } catch (Exception e) {
            throw StorageOperationException.operationFailed("Failed to delete objects: " + e.getMessage(), e);
        }
    }


    @Override
    public StorageObject getObjectInfo(String bucketName, String objectKey) {
        checkConnection();
        try {
            StatObjectResponse stat = client.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectKey)
                            .build()
            );

            return new StorageObject(
                    objectKey,
                    objectKey.substring(objectKey.lastIndexOf('/') + 1),
                    stat.size(),
                    stat.lastModified().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(),
                    false,
                    stat.etag()
            );
        } catch (Exception e) {
            throw StorageOperationException.objectNotFound(objectKey);
        }
    }

    @Override
    public StorageType getStorageType() {
        return StorageType.MINIO;
    }

    @Override
    public String generatePresignedUrl(String bucketName, String objectKey, int expiry) {
        checkConnection();
        try {
            return client.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(io.minio.http.Method.GET)
                            .bucket(bucketName)
                            .object(objectKey)
                            .expiry(expiry)
                            .build()
            );
        } catch (Exception e) {
            throw StorageOperationException.operationFailed("Failed to generate presigned URL: " + e.getMessage(), e);
        }
    }

    @Override
    public void uploadFileWithProgress(String bucketName, String objectKey, File file, ProgressTracker progressTracker) {
        checkConnection();
        try {

            // 创建带进度跟踪的输入流
            try (FileInputStream fis = new FileInputStream(file);
                 java.io.BufferedInputStream bufferedInput = new java.io.BufferedInputStream(fis, 64 * 1024); // 64KB缓冲
                 ProgressInputStream progressStream = new ProgressInputStream(bufferedInput, progressTracker)) {

                client.putObject(
                        PutObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectKey)
                                .stream(progressStream, file.length(), -1)
                                .build()
                );
            }
        } catch (Exception e) {
            throw StorageOperationException.operationFailed("Failed to upload file with progress: " + e.getMessage(), e);
        }
    }

    @Override
    public void uploadFileWithProgress(String bucketName, String objectKey, InputStream inputStream, long contentLength, ProgressTracker progressTracker) {
        checkConnection();
        try {

            // 创建带进度跟踪的输入流
            try (java.io.BufferedInputStream bufferedInput = new java.io.BufferedInputStream(inputStream, 64 * 1024); // 64KB缓冲
                 ProgressInputStream progressStream = new ProgressInputStream(bufferedInput, progressTracker)) {
                client.putObject(
                        PutObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectKey)
                                .stream(progressStream, contentLength, -1)
                                .build()
                );
            }
        } catch (Exception e) {
            throw StorageOperationException.operationFailed("Failed to upload file stream with progress: " + e.getMessage(), e);
        }
    }


}